﻿/*CLIENTAMF.NET - an open-source .NET alternative to Macromedia's .NET Flash Remoting Client
Copyright (C) 2010 Kiko Qiu - http://hi.baidu.com/kikoqiu

This project is based on the open-source project of AMF.NET, 
Part of the code is just a copy from it. You may check it out from sourceforge.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Fuel.AmfNet;

namespace kikoqiu
{
    public class AmfClient
    {
        #region Public Properties
        public HttpHelper HttpHelper = new HttpHelper();
        public string Gateway = null;
        public readonly string HttpContentType = "application/x-amf";

        public int CurrentTargetId
        {
            get
            {
                return CurrentTargetId;
            }
        }
        #endregion


        #region Call Methods
        public delegate void AmfCallback(string className,string methodName,string target,string callBackType, object data);
        public string AsynCall(string className, string methodName, object param, AmfCallback cb)
        {
            if (!ThreadWorking)
            {
                throw new Exception("Client not started. Call Start, or use Call");
            }
            if (Gateway == null)
            {
                throw new Exception("Gateway not set");
            }

            string target="/" + (currentTargetId++);
            CallBlock CallB = new CallBlock();
            CallB.cb = cb;
            CallB.body = new Body(methodName, className,target , param);

            lock (queueingCall)
            {
                queueingCall.Enqueue(CallB);
            }
            semLeftCalls.Release();
            return target;
        }

        public object Call(string className, string methodName, object param)
        {
            if (Gateway == null)
            {
                throw new Exception("Gateway not set");
            }

            string target="/"+(currentTargetId++);
            Message msg = new Message();
            msg.Bodies.Add(new Body(methodName, className, target, param));

            //prepareing call
            System.IO.MemoryStream ms = new System.IO.MemoryStream();
            Fuel.AmfNet.Serializer ss = new Fuel.AmfNet.Serializer(ms);
            ss.SerializeBodies(msg);
            ss.Close();

            System.IO.Stream st = null;

            try
            {
                st = HttpHelper.HttpPost(
                    Gateway
                    , HttpContentType
                    , ms.ToArray()).GetResponseStream();
            }
            catch
            {
                try
                {
                    st = HttpHelper.HttpPost(
                         Gateway
                         , HttpContentType
                         , ms.ToArray()).GetResponseStream();
                }
                catch
                {
                }
            }
            if (st == null)
            {
                throw new Exception("Cannot get stream.");
            }
            Message rmsg =
                    new Fuel.AmfNet.Deserializer(
                      st).DeserializeResponses();
            if ("onResult" != rmsg.Response[0].CallBack)
            {
                throw new Exception("Response type: " + rmsg.Response[0].CallBack);
            }
            return rmsg.Response[0].Data;
        }
        private int currentTargetId = 0;
        #endregion


        #region Async Call Worker Thread Methods
        System.Threading.Thread Thread;
        bool threadWorking = false;
        public bool ThreadWorking
        {
            get
            {
                return threadWorking;
            }
        }
        private object ThreadSyncRoot = new object();
        public void Start()
        {
            lock (ThreadSyncRoot)
            {
                if (!threadWorking)
                {
                    threadWorking = true;
                    semLeftCalls = new System.Threading.Semaphore(0, 9999999);
                    Thread = new System.Threading.Thread(new System.Threading.ThreadStart(ThreadMain));
                    Thread.Start();
                }
            }
        }
        public void Stop()
        {
            lock (ThreadSyncRoot)
            {
                if (threadWorking == true)
                {
                    semLeftCalls.Close();
                    semLeftCalls = null;
                    threadWorking = false;
                }
                Thread.Join();
            }
        }
        #endregion


        #region Async Call Helper Functions
        private struct CallBlock
        {
            public AmfCallback cb;
            public Fuel.AmfNet.Body body;
        }
        Queue<CallBlock> queueingCall = new Queue<CallBlock>();
        private System.Threading.Semaphore semLeftCalls;
        private void ThreadMain()
        {
            Message msg = new Message();
            Dictionary<string, CallBlock> callbacks = new Dictionary<string, CallBlock>();
            //msg.Version = 0;
            while (threadWorking)
            {
                while (threadWorking&&msg.Bodies.Count<10)
                {
                    if (semLeftCalls.WaitOne(100,false))
                    {
                        lock (queueingCall)
                        {
                            CallBlock cb = queueingCall.Dequeue();
                            msg.Bodies.Add(cb.body);
                            callbacks[cb.body.Target] = cb;
                        }
                    }
                    else break;
                }
                if (msg.Bodies.Count > 0)
                {
                    //prepareing call
                    System.IO.MemoryStream ms = new System.IO.MemoryStream();
                    Fuel.AmfNet.Serializer ss = new Fuel.AmfNet.Serializer(ms);
                    ss.SerializeBodies(msg);
                    ss.Close();

                    System.IO.Stream st = null;

                    try
                    {
                        st = HttpHelper.HttpPost(
                            Gateway
                            , HttpContentType
                            , ms.ToArray()).GetResponseStream();
                    }
                    catch
                    {
                        try
                        {
                            st = HttpHelper.HttpPost(
                                 Gateway
                                 , HttpContentType
                                 , ms.ToArray()).GetResponseStream();
                        }
                        catch
                        {
                        }
                    }
                    try
                    {
                        if (st == null)
                        {
                            throw new Exception("Cannot get stream.");
                        }
                        else
                        {
                            Message rmsg =
                                    new Fuel.AmfNet.Deserializer(
                                      st).DeserializeResponses();
                            foreach (Response r in rmsg.Response)
                            {
                                try
                                {
                                    CallBlock cbk=callbacks[r.Target];
                                    cbk.cb(cbk.body.MethodType, cbk.body.Method,r.Target, r.CallBack, r.Data);
                                }
                                catch
                                {
                                }
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        foreach (CallBlock cbk in callbacks.Values)
                        {
                            try
                            {
                                cbk.cb(cbk.body.MethodType,cbk.body.Method,cbk.body.Target,"onFault", e);
                            }
                            catch
                            {
                            }
                        }
                    }
                    finally
                    {
                        //reset
                        msg = new Message();
                        callbacks.Clear();
                    }
                }
            }
        }
        #endregion
    }
}
